package ruteodosciudades.algoritmo;

import ruteodosciudades.algoritmo.extra.ManageDB;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import ruteodosciudades.algoritmo.extra.Nodo;
import ruteodosciudades.algoritmo.extra.Camino;
import ruteodosciudades.algoritmo.extra.Arista;
import ruteodosciudades.algoritmo.extra.NodoImg;
import ruteodosciudades.algoritmo.extra.Registro;
import ruteodosciudades.exception.GrupoNueveIAException;

/**
 *
 * @author markos
 */
public class Dijkstra {

    private Map<String, Nodo> nodeMap = new HashMap<String, Nodo>();
    public static final Long INFINITY = Long.MAX_VALUE;
    private Nodo nodoDestino;
    private Nodo nodoOrigen;
    private ManageDB manage;
    private String startName;
    private String destName;

    public String getDestName() {
        return destName;
    }

    public void setDestName(String destName) {
        this.destName = destName;
    }

    public String getStartName() {
        return startName;
    }

    public void setStartName(String startName) {
        this.startName = startName;
    }

    public Dijkstra(String dataBase) {
        manage = new ManageDB(dataBase);
        buildGraph();
    }

    public Dijkstra(String startName, String destName, String dataBase) {
        this.startName = startName;
        this.destName = destName;
        System.out.println("En dijkstra -- inicio : " + startName + " - fin : " + destName);
        manage = new ManageDB(dataBase);
        buildGraph();
    }

    public Map<String, Nodo> getNodeMap() {
        return nodeMap;
    }

    public void setNodeMap(Map<String, Nodo> nodeMap) {
        this.nodeMap = nodeMap;
    }

    public Nodo getNodoDestino() {
        return nodoDestino;
    }

    public void setNodoDestino(String nodoDestino) {
        this.nodoDestino = nodeMap.get(nodoDestino);
    }

    public Nodo getNodoOrigen() {
        return nodoOrigen;
    }

    public void setNodoOrigen(String nodoOrigen) {
        this.nodoOrigen = nodeMap.get(nodoOrigen);
    }

    /**
     * Reinicia los valores del grafo.
     *
     */
    private void clearAll() {
        for (Nodo v : nodeMap.values()) {
            v.reset();
        }
    }

    private void doHeuristica() {
        NodoImg nMeta = manage.findNodo(nodeMap.get(destName).getName());
        for (NodoImg nActual : manage.getListaNodosConCoordenadas()) {
            nActual.getNodo().setDistLineaRecta(Math.sqrt(Math.pow(nMeta.getX() - nActual.getX(), 2) +
                    Math.pow(nMeta.getY() - nActual.getY(), 2)));
        }
    }

    /**
     * Ejecuta el algoritmo de dijkstra para el camino mas corto.
     * Utiliza una cola de prioridad como estructura de datos para la estrategia
     * de costo uniforme.
     *
     * @param startName
     */
    public void doDijkstra() {
        PriorityQueue<Camino> pq = new PriorityQueue<Camino>();

        Nodo start = nodeMap.get(this.startName);
        if (start == null) {
            throw new NoSuchElementException("Arista not found");
        }

        clearAll();
        pq.add(new Camino(start, 0));
        start.dist = 0;

        int nodesSeen = 0;
        while (!pq.isEmpty() && nodesSeen < nodeMap.size()) {
            Camino vrec = pq.remove();
            Nodo v = vrec.dest;
            if (v.scratch != 0) // ya se proceso
            {
                continue;
            }

            v.scratch = 1;
            nodesSeen++;

            for (Arista n : v.adj) {
                Nodo w = n.dest;
                long cvw = n.cost;

                if (cvw < 0) {
                    throw new GrupoNueveIAException("Arista negativa");
                }

                if (w.getFnValue() > v.getFnValue() + cvw + w.getDistLineaRecta()) {
                    w.dist = v.dist + cvw;
                    w.prev = v;
                    pq.add(new Camino(w, w.dist));
                }
            }
        }
    }

    /**
     * Agrega un nodo al grafo.
     *
     * @param sourceName
     * @param destName
     * @param cost
     */
    public void addNodo(String sourceName, String destName, long cost) {
        Nodo v = getNodo(sourceName);
        Nodo w = getNodo(destName);
        v.adj.add(new Arista(w, cost));
    }

    /**
     * Obtiene un nodo nodeName desde el grafo.
     *
     * @param nodeName
     * @return
     */
    private Nodo getNodo(String nodeName) {
        Nodo n = nodeMap.get(nodeName);
        if (n == null) {
            n = new Nodo(nodeName);
            nodeMap.put(nodeName, n);
        }
        return n;
    }

    /***   lizj      ***/
    public void buildGraph() {

        List<Registro> listaNodos = new ArrayList();
        try {
            listaNodos = manage.getListaNodos();
        } catch (SQLException ex) {
            Logger.getLogger(Dijkstra.class.getName()).log(Level.SEVERE, null, ex);
        }

        System.out.println("-----------------------------------");
        System.out.println("LISTA DE REGISTROS RECUPERADOS DE LA BD");
        System.out.println("-----------------------------------");

        // insertamos las aristas
        for (Registro nImg : listaNodos) {
            System.out.println(nImg.getSourceName() + ": " + nImg.getDestName() + " " + nImg.getCost());
            addNodo(nImg.getSourceName(), nImg.getDestName(), nImg.getCost());
        }

        processRequest(null, this);
        
        System.out.println("-----------------------------------");
        System.out.println("Cantidad de registros:" + listaNodos.size());
        System.out.println("-----------------------------------");
    }

    public static void main(String args[]){
        new Dijkstra("ruteo");
    }

    public static boolean processRequest(BufferedReader in, Dijkstra d) {
        String startName = null;
        String destName = null;

        try {
            in = new BufferedReader(new InputStreamReader(System.in));
            System.out.print("Enter start node:");
            if ((startName = in.readLine()) == null) {
                return false;
            }
            System.out.print("Enter destination node:");
            if ((destName = in.readLine()) == null) {
                return false;
            }
            d.setDestName(destName);
            d.setStartName(startName);
            d.doHeuristica();
            d.doDijkstra();
            d.printPath(destName);

        } catch (IOException e) {
            System.err.println(e);
        } catch (NoSuchElementException e) {
            System.err.println(e);
        } catch (GrupoNueveIAException e) {
            System.err.println(e);
        }
        return true;
    }

    /**
     *
     * Imprime el camino al nodo destName.
     *
     * @param destName
     */
    public void printPath(String destName) {
        Nodo w = nodeMap.get(destName);
        if (w == null) {
//            throw new NoSuchElementException("Destino no encontrado");
            System.out.println(destName + " destino no encontrado");
        } else if (w.dist == INFINITY) {
            System.out.println(destName + " es inalcanzable");
        } else {
            System.out.print("(Costo : " + w.dist + ") ");
            printPath(w);
            System.out.println();
        }
    }

    private void printPath(Nodo dest) {
        if (dest.prev != null) {
            printPath(dest.prev);
            System.out.print(" a ");
        }
        System.out.print(dest.name);
    }
}
